import WLSessionModule from "@/main/weila_session_module";
import { FSM_NODE, FSM_PTT_EVENT_CONSTANT, ptt_fsm } from "@/fsm/weila_ptt_fsm";
import { interpret } from "xstate";
// @ts-ignore
import debug from "debug";
import {
    WL_IDbAudioData,
    WL_IDbMsgData,
    WL_IDbMsgDataStatus,
    WL_IDbMsgDataType, WL_IDbSession
} from "@/proto/weilapb_external_data";
import {
    WL_PromiseCallback,
    WL_PttAudioItem,
    WL_PttPacket,
    WL_PttPackType,
    WL_PttPayload,
    WLPlayerType
} from "@/core/weila_datas";
import { WLAudioCore } from "@/audio/audio_core";
import {
    calculateOpusDataFrame,
    decompositionAudioData,
    fetchWithTimeout,
    getOpusDataListFromPttData
} from "@/core/weila_utils";
import { RingDataID, weila_playRing } from "@/audio/weila_ring_player";
import { Weila_MemDB } from "@/mem_store/weila_db";

const PLOG = debug('[SDK:PCORE]')

interface PttCoreListener {
    onRecordPttPacketInd(pttPacket: WL_PttPacket): void;
    onAudioItemPlayEnd(msgData: WL_IDbMsgData): void;
    onPlayAudioData(msgData: WL_IDbMsgData, frameCount: number): void;
    onCurrentPlayAudioMsg(msgData: WL_IDbMsgData): void;
    onRecorderErrorInd(error: string): void;
}

interface PromiseCallback {
    succ: (arg: any) => any,
    fail: (reason: any) => void
}

interface EventParameter {
    audioItem: WL_PttAudioItem|undefined,
    callback: PromiseCallback
}

interface ServiceResult {
    result: any;
    extra: any;
}

class ServiceError extends Error {
    constructor(public extra: any, errorMsg: any) {
        super(errorMsg);
    }
}

interface WL_TalkingInfo {
    seqOfPackage: number;
    curMarker: WL_PttPackType;
    callback?: WL_PromiseCallback;
    frameBufferCache: Uint8Array[];
    stopByErr: boolean;
}

class WL_PttCore {
    private static pttSeq: number = 0;
    private static readonly FRAME_COUNT = 25;
    private ptt_service: any;
    private listener: PttCoreListener|undefined;
    private audioCore: WLAudioCore|undefined;
    private curNode: FSM_NODE;
    private talkingInfo: WL_TalkingInfo|undefined;
    private playingAudioList: string[];
    private playingAudioMap: Map<string, WL_PttAudioItem>;
    private playingAudioItem: WL_PttAudioItem|undefined;
    private playingStreamAudioItem: WL_PttAudioItem|undefined;
    private toNode: FSM_NODE;
    private fromNode: FSM_NODE;

    constructor(private sessionModule: WLSessionModule) {
        this.playingAudioList = [];
        this.playingAudioMap = new Map<string, WL_PttAudioItem>();
        this.curNode = FSM_NODE.FSM_IDLE_NODE;
        this.toNode = FSM_NODE.FSM_IDLE_NODE;
        this.fromNode = FSM_NODE.FSM_IDLE_NODE;
        this.talkingInfo = undefined;

        const ptt_fsm_ext = ptt_fsm.withContext({
            "pttCore": this
        }).withConfig({
            actions: {
                "onIdleEntry": this.onIdleEntry.bind(this),
                "onIdleExit": this.onIdleExit.bind(this),
                "onSinglePlayDone": this.onSinglePlayDone.bind(this),
                "onSinglePlayError": this.onSinglePlayError.bind(this),
                "onStreamPlaySucc": this.onStreamPlaySucc.bind(this),
                "onStreamPlayError": this.onStreamPlayError.bind(this),
                "onTalkReqSucc": this.onTalkReqSucc.bind(this),
                "onTalkReqError": this.onTalkReqError.bind(this),
                "onTalkingEntry": this.onTalkingEntry.bind(this),
                "onTalkingExit": this.onTalkingExit.bind(this),
                "onSinglePlayingEntry": this.onSinglePlayingEntry.bind(this),
                "onSinglePlayingExit": this.onSinglePlayingExit.bind(this),
                "onFeedDataAct": this.onFeedDataAct.bind(this),
                "onStreamPlayingEntry": this.onStreamPlayingEntry.bind(this),
                "onStreamPlayingExit": this.onStreamPlayingExit.bind(this),
                "onSingleStopSucc": this.onSingleStopSucc.bind(this),
                "onSingleStopError": this.onSingleStopError.bind(this),
                "onSingleStopEntry": this.onSingleStopEntry.bind(this),
                "onSingleStopExit": this.onSingleStopExit.bind(this),
                "onTalkStopSucc": this.onTalkStopSucc.bind(this),
                "onTalkStopError": this.onTalkStopError.bind(this),
                "onStreamPlayStopSucc": this.onStreamPlayStopSucc.bind(this),
                "onStreamPlayStopError": this.onStreamPlayStopError.bind(this),
                "playSingleFromSingle": this.playSingleFromSingle.bind(this),
                "reqTalkFromSingle": this.reqTalkFromSingle.bind(this),
                "reqTalkFromStream": this.reqTalkFromStream.bind(this),
                "playSingleFromStream": this.playSingleFromStream.bind(this),
                "playSingFromIdle": this.playSingFromIdle.bind(this),
                "playStreamFromIdle": this.playStreamFromIdle.bind(this),
                "onStreamPlayNextAct": this.onStreamPlayNextAct.bind(this),
                "onStreamPlayerStopEntry": this.onStreamPlayerStopEntry.bind(this)
            },

            services: {
                "playSingle": this.playSingle.bind(this),
                "playStream": this.playStream.bind(this),
                "startTalk": this.startTalk.bind(this),
                "stopSinglePlayer": this.stopSinglePlayer.bind(this),
                "stopTalk": this.stopTalk.bind(this),
                "streamStop": this.streamStop.bind(this),
            },
        });

        this.ptt_service = interpret(ptt_fsm_ext).onTransition((state, event) => {
            PLOG("事件:%o 状态:%o ====> 状态:%o", event, state.history ? state.history.value : "初始状态", state.value);
        }).start();
    }

    public setListener(listener: PttCoreListener) {
        this.listener = listener;
    }

    public init() {
        this.audioCore = new WLAudioCore();
        this.audioCore.initAudio(16000);
    }

    public muteSession(sessionId: string, mute: boolean) {
        Weila_MemDB.getInstance().enableSessionMute(sessionId, mute);
        if (mute) {
            const index = this.playingAudioList.findIndex(value => {
                const audioItem = this.playingAudioMap.get(value);
                if (audioItem) {
                    return audioItem.msgData.sessionId == sessionId;
                }
                return false;
            })

            if (this.playingStreamAudioItem && this.playingStreamAudioItem.msgData.sessionId == sessionId) {
                this.playNextStreamAudioItem();
            }else {
                if (index != -1) {
                    this.playingAudioList.splice(index, 1);
                }
            }
        }
    }

    public muteAllSession(sessionIdList: string[], mute: boolean) {
        Weila_MemDB.getInstance().enbaleAllSessionsMute(sessionIdList, mute);
        this.playingAudioList = [];
        if (this.playingStreamAudioItem) {
            this.ptt_service.send(FSM_PTT_EVENT_CONSTANT.FSM_PLAY_STOP_EVT);
        }
    }

    public stopSingleAudio(): boolean {
        this.ptt_service.send(FSM_PTT_EVENT_CONSTANT.FSM_PLAY_STOP_EVT);
        return true;
    }

    public async playSingleAudio(msgData: WL_IDbMsgData): Promise<boolean> {
        if (msgData && msgData.msgType != WL_IDbMsgDataType.WL_DB_MSG_DATA_AUDIO_TYPE) {
            return Promise.reject(new Error("音频消息类型不对或消息是空值"));
        }

        if (!msgData.audioData || (!msgData.audioData.audioUrl && !msgData.audioData.data)) {
            return Promise.reject(new Error("音频数据不存在"));
        }

        if (this.curNode == FSM_NODE.FSM_TALK_NODE) {
            return Promise.reject(new Error("当前处于发言中"));
        }

        return new Promise(async (resolve, reject) => {
            const parameter = {} as EventParameter;
            parameter.audioItem = {} as WL_PttAudioItem;
            parameter.audioItem.msgData = msgData;
            parameter.audioItem.id = msgData.combo_id;
            parameter.audioItem.playerType = WLPlayerType.SINGLE_PLAYER;
            parameter.audioItem.playIndex = 0;
            parameter.audioItem.isCompleted = true;
            parameter.audioItem.priority = 0;
            parameter.audioItem.shouldSave = true;
            parameter.callback = {} as PromiseCallback;
            parameter.callback.succ = resolve;
            parameter.callback.fail = reject;

            if (msgData.audioData!.data) {
                parameter.audioItem.payloadList = decompositionAudioData(msgData.audioData!.data);
                this.ptt_service.send(FSM_PTT_EVENT_CONSTANT.FSM_SINGLE_PLAY_EVT, {parameter: parameter});
            } else if (msgData.audioData!.audioUrl) {
                let matchResult = msgData.audioData!.audioUrl.match(/(http|https):\/\/([^\/]+)\/(.+)/i);
                if (matchResult) {
                    const newUrl = '/audio/' + matchResult[3];
                    try {
                        const result = await fetchWithTimeout(newUrl, {method: 'GET'}, 5000);
                        if (result.ok) {
                            const audioData = {} as WL_IDbAudioData;
                            const data = await result.arrayBuffer();
                            audioData.data = new Uint8Array(data.slice(10));
                            audioData.frameCount = calculateOpusDataFrame(audioData.data);
                            parameter.audioItem.payloadList = decompositionAudioData(audioData.data);

                            msgData.audioData!.audioUrl = undefined;
                            msgData.audioData!.data = audioData.data;
                            msgData.audioData!.frameCount = audioData.frameCount;
                            this.ptt_service.send(FSM_PTT_EVENT_CONSTANT.FSM_SINGLE_PLAY_EVT, {parameter: parameter});
                        } else {
                            PLOG('获取数据异常 %o', result);
                            reject(new Error(`result is ${result}`));
                        }
                    } catch (e) {
                        PLOG('获取数据异常 %o', e);
                        reject(e);
                    }
                }
            }
        });
    }

    public playNextStreamAudioItem() {
        this.ptt_service.send(FSM_PTT_EVENT_CONSTANT.FSM_PLAY_NEXT_EVT);
    }

    public putAudioToStream(msgData: WL_IDbMsgData, shouldSave: boolean) {
        if (msgData.msgType == WL_IDbMsgDataType.WL_DB_MSG_DATA_AUDIO_TYPE) {
            let shouldPlayStream: boolean = (this.playingStreamAudioItem == undefined && this.playingAudioList.length == 0);
            const audioItem = {} as WL_PttAudioItem;
            const shouldMute = Weila_MemDB.getInstance().isSessionMute(msgData.sessionId);
            audioItem.id = msgData.combo_id;
            audioItem.playerType = WLPlayerType.STREAM_PLAYER;
            audioItem.isCompleted = true;
            audioItem.msgData = msgData;
            audioItem.priority = 0;
            audioItem.playIndex = 0;

            if (msgData.audioData!.data) {
                audioItem.payloadList = decompositionAudioData(audioItem.msgData!.audioData!.data!);
                audioItem.shouldSave = shouldSave;
                if (!shouldMute) {
                    this.playingAudioMap.set(audioItem.id, audioItem);
                    this.playingAudioList.push(audioItem.id);
                    if (shouldPlayStream) {
                        this.ptt_service.send(FSM_PTT_EVENT_CONSTANT.FSM_STREAM_PLAY_EVT);
                    }
                }
            }else if (msgData.audioData!.audioUrl && msgData.audioData!.audioUrl.length > 0) {

                let matchResult = msgData.audioData!.audioUrl.match(/(http|https):\/\/([^\/]+)\/(.+)/i);
                if (matchResult) {
                    const newUrl = '/audio/' + matchResult[3];
                    fetchWithTimeout(newUrl, {method: 'GET'}, 5000).then(async result => {
                        if (result.ok) {
                            try {
                                const audioData = {} as WL_IDbAudioData;
                                const data = await result.arrayBuffer();
                                audioData.data = new Uint8Array(data.slice(10));
                                audioData.frameCount = calculateOpusDataFrame(audioData.data);
                                audioItem.payloadList = decompositionAudioData(audioData.data);
                                audioItem.shouldSave = shouldSave;

                                msgData.audioData!.audioUrl = undefined;
                                msgData.audioData!.data = audioData.data;
                                msgData.audioData!.frameCount = audioData.frameCount;

                                if (!shouldMute) {
                                    this.playingAudioMap.set(audioItem.id, audioItem);
                                    this.playingAudioList.push(audioItem.id);
                                    if (shouldPlayStream) {
                                        this.ptt_service.send(FSM_PTT_EVENT_CONSTANT.FSM_STREAM_PLAY_EVT);
                                    }
                                }
                            } catch (e) {
                                PLOG("获取语音数据出错 %o", e);
                            }
                        }
                    }).catch(reason => {
                        PLOG('获取数据异常 %o', reason);
                    })
                }
            }
        }
    }

    private fillPttAudioData(audioItem: WL_PttAudioItem, msgData: WL_IDbMsgData) {
        if (audioItem.msgData.audioData == undefined) {
            audioItem.msgData.audioData = {} as WL_IDbAudioData;
            audioItem.msgData.audioData.data = new Uint8Array(0);
            audioItem.msgData.audioData.frameCount = 0;
        }

        if (msgData.msgType == WL_IDbMsgDataType.WL_DB_MSG_DATA_PTT_TYPE) {
            const newBufferLength = audioItem.msgData.audioData!.data!.length + msgData.pttData!.data.length;
            const newBuffer = new Uint8Array(newBufferLength);
            newBuffer.set(audioItem.msgData.audioData!.data!, 0);
            newBuffer.set(msgData.pttData!.data, audioItem.msgData.audioData!.data!.length);
            audioItem.msgData.audioData.data = newBuffer;
            audioItem.msgData.audioData.frameCount += msgData.pttData!.frameCount;
        }
    }

    public putAudioPacketToStream(msgData: WL_IDbMsgData): {msgData: WL_IDbMsgData|undefined, newMsg: boolean} {
        const shouldMute = Weila_MemDB.getInstance().isSessionMute(msgData.sessionId);

        PLOG("putAudioPacketToStream shouldMute:", shouldMute, msgData.sessionId);

        if (msgData.msgType === WL_IDbMsgDataType.WL_DB_MSG_DATA_PTT_TYPE) {
            let newMsg: boolean = false;
            const id = msgData.combo_id;
            let audioItem = {} as WL_PttAudioItem;
            const payload = {} as WL_PttPayload;

            audioItem.id = id;
            payload.data = msgData.pttData!.data
            payload.frameCount = msgData.pttData!.frameCount;

            if (this.playingAudioMap.has(id)) {
                PLOG('消息在播放队列中 %o', id);
                audioItem = this.playingAudioMap.get(id)!;
                if (audioItem.recvWaitTimerId) {
                    clearTimeout(audioItem.recvWaitTimerId);
                    audioItem.recvWaitTimerId = null;
                }
                this.fillPttAudioData(audioItem, msgData);
                audioItem.msgData.msgType = WL_IDbMsgDataType.WL_DB_MSG_DATA_AUDIO_TYPE;
                audioItem.isCompleted = msgData.pttData!.mark == 0x02 || msgData.pttData!.mark == 0x03;
            } else {
                PLOG('消息不在播放队列中 %o', id);
                if (!shouldMute) {
                    this.playingAudioList.push(id);
                }
                audioItem.shouldSave = true;
                audioItem.msgData = msgData;
                audioItem.msgData.status = WL_IDbMsgDataStatus.WL_DB_MSG_DATA_STATUS_NEW;
                this.fillPttAudioData(audioItem, msgData);
                audioItem.msgData.msgType = WL_IDbMsgDataType.WL_DB_MSG_DATA_AUDIO_TYPE;
                audioItem.isCompleted = msgData.pttData!.mark == 0x02 || msgData.pttData!.mark == 0x03;

                audioItem.playerType = WLPlayerType.STREAM_PLAYER;
                audioItem.priority = 0;
                audioItem.payloadList = [];
                audioItem.playIndex = 0;
                newMsg = true;
            }

            if (audioItem.shouldSave) {
                try {
                    Weila_MemDB.getInstance().putMsgData(audioItem.msgData);
                    PLOG('保存音频消息成功');
                } catch (e) {
                    PLOG('保存音频消息异常:', e);
                }
            }

            audioItem.payloadList.push(payload);

            if (audioItem.isCompleted) {
                PLOG('音频消息是完整的')
                audioItem.msgData.pttData = undefined;
                audioItem.msgData.msgType = WL_IDbMsgDataType.WL_DB_MSG_DATA_AUDIO_TYPE;
            }else {
                PLOG('音频消息是不完整的')
                audioItem.recvWaitTimerId = setTimeout(async () => {
                    audioItem.isCompleted = true;
                    audioItem.msgData.pttData = undefined;
                    audioItem.msgData.msgType = WL_IDbMsgDataType.WL_DB_MSG_DATA_AUDIO_TYPE;

                    if (!Weila_MemDB.getInstance().isSessionMute(msgData.sessionId) &&
                        this.playingStreamAudioItem == audioItem) {
                        if (this.listener != undefined) {
                            this.listener.onPlayAudioData(audioItem.msgData, -1);
                        }
                    }

                    try {
                        Weila_MemDB.getInstance().putMsgData(audioItem.msgData);
                        PLOG('超时保存消息成功');
                    }catch (e) {
                        PLOG('超时保存消息失败 %o', e);
                    }

                }, 30000);
            }

            this.playingAudioMap.set(id, audioItem);
            if (shouldMute) {
                if (audioItem.isCompleted) {
                    this.playingAudioMap.delete(id);
                }
            }

            PLOG("curNode and shouldMute", this.curNode, shouldMute);
            if (this.curNode != FSM_NODE.FSM_STREAM_PLAY_NODE) {
                if (!shouldMute) {
                    this.ptt_service.send(FSM_PTT_EVENT_CONSTANT.FSM_STREAM_PLAY_EVT);
                }
            } else if (this.curNode == FSM_NODE.FSM_STREAM_PLAY_NODE) {
                if (!shouldMute) {
                    this.ptt_service.send(FSM_PTT_EVENT_CONSTANT.FSM_FEED_DATA_EVT, {audioItem: this.playingStreamAudioItem});
                }
            }

            return {msgData: audioItem.msgData, newMsg: newMsg};
        }

        return {msgData: undefined, newMsg: false};
    }

    public async requestTalk(): Promise<boolean> {
        return new Promise<boolean>((resolve, reject) => {
            const callback = {} as PromiseCallback;
            callback.succ = resolve;
            callback.fail = reject;
            this.ptt_service.send(FSM_PTT_EVENT_CONSTANT.FSM_REQUEST_TALK_EVT, {callback: callback});
        })
    }

    public releaseTalk(error?: any) {
        this.ptt_service.send(FSM_PTT_EVENT_CONSTANT.FSM_TALK_STOP_EVT, {error: error});
    }

    private singlePlayEndCallback(playerType: number) {
        const msgData = this.playingAudioItem!.msgData;
        this.playingAudioItem = undefined;
        this.ptt_service.send(FSM_PTT_EVENT_CONSTANT.FSM_PLAY_END_EVT, {playerType: playerType, msgData: msgData});
    }


    // actions
    private playSingFromIdle(context: any, event: any, actionMeta: any) {
        PLOG("playSingFromIdle: context:%o event:%o actionMeta:%o", context, event, actionMeta);
        this.fromNode = FSM_NODE.FSM_IDLE_NODE;
        this.toNode = FSM_NODE.FSM_SINGLE_PLAY_NODE;
    }

    private playStreamFromIdle(context: any, event: any, actionMeta: any) {
        PLOG("playStreamFromIdle: context:%o event:%o actionMeta:%o", context, event, actionMeta);
        this.fromNode = FSM_NODE.FSM_IDLE_NODE;
        this.toNode = FSM_NODE.FSM_STREAM_PLAY_NODE;
    }

    private onIdleEntry(context: any, event: any, actionMeta: any) {
        PLOG("onIdleEntry: context:%o event:%o actionMeta:%o", context, event, actionMeta);
        this.curNode = FSM_NODE.FSM_IDLE_NODE;

        if (this.playingStreamAudioItem != undefined ||
        this.playingAudioList.length > 0) {
            this.ptt_service.send(FSM_PTT_EVENT_CONSTANT.FSM_STREAM_PLAY_EVT)
        }
    }

    private onIdleExit(context: any, event: any, actionMeta: any) {
        PLOG("onIdleExit: context:%o event:%o actionMeta:%o", context, event, actionMeta);
    }

    private onSinglePlayDone(context: any, event: any, actionMeta: any) {
        PLOG("onSinglePlayDone: context:%o event:%o actionMeta:%o", context, event, actionMeta);
        const serviceRet: ServiceResult = event.data as ServiceResult;
        PLOG("serviceRet %o", serviceRet);
        if (serviceRet.extra.callback) {
            serviceRet.extra.callback.succ(serviceRet.result);
        }
    }

    private onSinglePlayError(context: any, event: any, actionMeta: any) {
        PLOG("onSinglePlayError: context:%o event:%o actionMeta:%o", context, event, actionMeta);
        const serviceError: ServiceError = event.data as ServiceError;
        if (serviceError.extra.callback) {
            serviceError.extra.callback.fail(serviceError.message);
        }
    }

    private onStreamPlaySucc(context: any, event: any, actionMeta: any) {
        PLOG("onStreamPlaySucc: context:%o event:%o actionMeta:%o", context, event, actionMeta);
        const serviceRet: ServiceResult = event.data as ServiceResult;
        PLOG("serviceRet %o", serviceRet);
    }

    private onStreamPlayError(context: any, event: any, actionMeta: any) {
        PLOG("onStreamPlayError: context:%o event:%o actionMeta:%o", context, event, actionMeta);
        const serviceError: ServiceError = event.data as ServiceError;
    }

    private onTalkReqSucc(context: any, event: any, actionMeta: any) {
        PLOG("onTalkReqSucc: context:%o event:%o actionMeta:%o", context, event, actionMeta);
        const serviceRet: ServiceResult = event.data as ServiceResult;
        PLOG("serviceRet %o", serviceRet);
        if (serviceRet.extra.callback) {
            serviceRet.extra.callback.succ(serviceRet.result);
        }
    }

    private onTalkReqError(context: any, event: any, actionMeta: any) {
        PLOG("onTalkReqError: context:%o event:%o actionMeta:%o", context, event, actionMeta);
        const serviceError: ServiceError = event.data as ServiceError;
        if (serviceError.extra.callback) {
            serviceError.extra.callback.fail(serviceError.message);
        }
    }

    private onTalkingEntry(context: any, event: any, actionMeta: any) {
        PLOG("onTalkingEntry: context:%o event:%o actionMeta:%o", context, event, actionMeta);
        this.curNode = FSM_NODE.FSM_TALK_NODE;
    }

    private onTalkingExit(context: any, event: any, actionMeta: any) {
        PLOG("onTalkingExit: context:%o event:%o actionMeta:%o", context, event, actionMeta);
        this.curNode = FSM_NODE.FSM_IDLE_NODE;
    }

    private onSinglePlayingEntry(context: any, event: any, actionMeta: any) {
        PLOG("onSinglePlayingEntry: context:%o event:%o actionMeta:%o", context, event, actionMeta);
        this.curNode = FSM_NODE.FSM_SINGLE_PLAY_NODE;
        this.ptt_service.send(FSM_PTT_EVENT_CONSTANT.FSM_FEED_DATA_EVT, {audioItem: this.playingAudioItem});
    }

    private onSinglePlayingExit(context: any, event: any, actionMeta: any) {
        PLOG("onSinglePlayingExit: context:%o event:%o actionMeta:%o", context, event, actionMeta);
        this.curNode = FSM_NODE.FSM_IDLE_NODE;
    }

    private onFeedDataAct(context: any, event: any, actionMeta: any) {
        PLOG("onFeedDataAct: context:%o event:%o actionMeta:%o", context, event, actionMeta);
        const audioItem = event.audioItem;

        PLOG("playIndex:%o", audioItem);
        if (audioItem && audioItem.playIndex < audioItem.payloadList.length) {
            const opusData = audioItem.payloadList[audioItem.playIndex++];
            const opusRawDataList = getOpusDataListFromPttData(opusData.data);
            this.audioCore!.feedPlayerCodedData(audioItem.playerType, opusRawDataList);

            // 通知外面目前播放消息的第几帧
            if (this.listener !== undefined) {
                this.listener.onPlayAudioData(audioItem.msgData, audioItem.playIndex);
            }

            if (audioItem.isCompleted && audioItem.playIndex == audioItem.payloadList.length) {
                if (this.listener !== undefined) {
                    this.listener.onPlayAudioData(audioItem.msgData, -1);
                }
            }

            if (audioItem.playIndex == audioItem.payloadList.length && audioItem.isCompleted) {
                this.audioCore!.feedPlayerCodedData(audioItem.playerType, undefined);
            } else if (audioItem.payloadList.length > audioItem.playIndex) {
                this.ptt_service.send(FSM_PTT_EVENT_CONSTANT.FSM_FEED_DATA_EVT, {audioItem: audioItem});
            }
        }
    }

    private onStreamPlayNextAct(context: any, event: any, actionMeta: any) {
        PLOG("onStreamPlayNextAct: context:%o event:%o actionMeta:%o", context, event, actionMeta);
        this.playingAudioList.shift();
    }

    private onStreamPlayingEntry(context: any, event: any, actionMeta: any) {
        PLOG("onStreamPlayingEntry: context:%o event:%o actionMeta:%o", context, event, actionMeta);
        this.curNode = FSM_NODE.FSM_STREAM_PLAY_NODE;
        this.ptt_service.send(FSM_PTT_EVENT_CONSTANT.FSM_FEED_DATA_EVT, {audioItem: this.playingStreamAudioItem});
    }

    private onStreamPlayingExit(context: any, event: any, actionMeta: any) {
        PLOG("onStreamPlayingExit: context:%o event:%o actionMeta:%o", context, event, actionMeta);
        this.curNode = FSM_NODE.FSM_IDLE_NODE;
    }

    private onSingleStopSucc(context: any, event: any, actionMeta: any) {
        PLOG("onSingleStopSucc: context:%o event:%o actionMeta:%o", context, event, actionMeta);
    }

    private onSingleStopError(context: any, event: any, actionMeta: any) {
        PLOG("onSingleStopError: context:%o event:%o actionMeta:%o", context, event, actionMeta);
        if (event.data.callback) {
            event.data.callback.fail(event.data.message);
        }
    }

    private onSingleStopEntry(context: any, event: any, actionMeta: any) {
        PLOG("onSingleStopEntry: context:%o event:%o actionMeta:%o", context, event, actionMeta);
        if (this.toNode == FSM_NODE.FSM_SINGLE_PLAY_NODE && this.fromNode == FSM_NODE.FSM_SINGLE_PLAY_NODE) {
            this.ptt_service.send(FSM_PTT_EVENT_CONSTANT.FSM_SINGLE_PLAY_EVT, {parameter: event.data.extra});
        }else if (this.toNode == FSM_NODE.FSM_TALK_NODE) {
            this.ptt_service.send(FSM_PTT_EVENT_CONSTANT.FSM_REQUEST_TALK_EVT, {callback: event.data.extra});
        }else {
            this.ptt_service.send(FSM_PTT_EVENT_CONSTANT.FSM_IDLE_EVT);
        }

        this.toNode = FSM_NODE.FSM_IDLE_NODE;
    }

    private onSingleStopExit(context: any, event: any, actionMeta: any) {
        PLOG("onSingleStopExit: context:%o event:%o actionMeta:%o", context, event, actionMeta);
    }

    private onTalkStopSucc(context: any, event: any, actionMeta: any) {
        PLOG("onTalkStopSucc: context:%o event:%o actionMeta:%o", context, event, actionMeta);
    }

    private onTalkStopError(context: any, event: any, actionMeta: any) {
        PLOG("onTalkStopError: context:%o event:%o actionMeta:%o", context, event, actionMeta);
    }

    private onStreamPlayStopSucc(context: any, event: any, actionMeta: any) {
        PLOG("onStreamStopSucc: context:%o event:%o actionMeta:%o", context, event, actionMeta);
    }

    private onStreamPlayStopError(context: any, event: any, actionMeta: any) {
        PLOG("onStreamStopError: context:%o event:%o actionMeta:%o", context, event, actionMeta);
        if (this.toNode == FSM_NODE.FSM_SINGLE_PLAY_NODE || this.toNode == FSM_NODE.FSM_TALK_NODE) {
            if (event.data.callback) {
                event.data.callback.fail(event.data.message);
            }
        }
    }

    private onStreamPlayerStopEntry(context: any, event: any, actionMeta: any) {
        PLOG("onStreamPlayerStopEntry: context:%o event:%o actionMeta:%o", context, event, actionMeta);
        if (this.toNode == FSM_NODE.FSM_TALK_NODE) {
            this.ptt_service.send(FSM_PTT_EVENT_CONSTANT.FSM_REQUEST_TALK_EVT);
        }else if (this.toNode == FSM_NODE.FSM_SINGLE_PLAY_NODE) {
            this.ptt_service.send(FSM_PTT_EVENT_CONSTANT.FSM_SINGLE_PLAY_EVT);
        }else {
            if (this.playingAudioList.length > 0) {
                this.ptt_service.send(FSM_PTT_EVENT_CONSTANT.FSM_STREAM_PLAY_EVT);
            }else {
                this.ptt_service.send(FSM_PTT_EVENT_CONSTANT.FSM_IDLE_EVT);
            }
        }
    }

    private async playSingleFromSingle(context: any, event: any, actionMeta: any) {
        PLOG("playSingleFromSingle: context:%o event:%o actionMeta:%o", context, event, actionMeta);
        this.toNode = FSM_NODE.FSM_SINGLE_PLAY_NODE;
        this.fromNode = FSM_NODE.FSM_SINGLE_PLAY_NODE;
    }

    private async reqTalkFromSingle(context: any, event: any, actionMeta: any) {
        PLOG("reqTalkFromSingle: context:%o event:%o actionMeta:%o", context, event, actionMeta);
        this.toNode = FSM_NODE.FSM_TALK_NODE;
        this.fromNode = FSM_NODE.FSM_SINGLE_PLAY_NODE;
    }

    private async reqTalkFromStream(context: any, event: any, actionMeta: any) {
        PLOG("reqTalkFromStream: context:%o event:%o actionMeta:%o", context, event, actionMeta);
        this.toNode = FSM_NODE.FSM_TALK_NODE
    }

    private async playSingleFromStream(context: any, event: any, actionMeta: any) {
        PLOG("playSingleFromStream: context:%o event:%o actionMeta:%o", context, event, actionMeta);
        this.fromNode = FSM_NODE.FSM_STREAM_PLAY_NODE;
        this.toNode = FSM_NODE.FSM_SINGLE_PLAY_NODE;
    }


    // services
    private async playSingle(context: any, event: any): Promise<ServiceResult> {
        PLOG("playSingle: context:%o event:%o", context, event);
        const parameter = event.parameter as EventParameter;

        try {
            await weila_playRing(RingDataID.RING_PLAYER_START_ID);
        }catch (e) {
            PLOG("播放铃声失败:%o", e);
        }

        try {
            this.playingAudioItem = parameter.audioItem;
            await this.audioCore!.startPlayer(WLPlayerType.SINGLE_PLAYER);
            this.audioCore!.setPlayerEndCb(this.singlePlayEndCallback.bind(this));
        }catch (e) {
            return Promise.reject(new ServiceError(parameter, e));
        }

        return {
            result: true,
            extra: parameter
        }
    }

    private peekNextAudioItem(): WL_PttAudioItem|undefined {
        if (this.playingAudioList.length > 0) {
            let audioItem = this.playingAudioMap.get(this.playingAudioList[0]);
            return audioItem;
        }

        return undefined;
    }

    private getNextPlayingItem(): WL_PttAudioItem|undefined {
        while (this.playingAudioList.length > 0) {
            const audioItem = this.playingAudioMap.get(this.playingAudioList[0]);
            if (audioItem) {
                audioItem.msgData.status = WL_IDbMsgDataStatus.WL_DB_MSG_DATA_STATUS_READ;
                if (audioItem.shouldSave) {
                    Weila_MemDB.getInstance().putMsgData(audioItem.msgData);
                }

                if (this.listener !== undefined) {
                    this.listener.onCurrentPlayAudioMsg(audioItem.msgData);
                }

                return audioItem;
            }else {
                this.playingAudioList.shift();
            }
        }

        return undefined;
    }

    private onStreamItemPlayEndCb(playerType: number) {
        PLOG("播放结束 %o", playerType);
        this.ptt_service.send(FSM_PTT_EVENT_CONSTANT.FSM_PLAY_END_EVT);
    }

    private async playStream(context: any, event: any): Promise<ServiceResult> {
        PLOG("playStream: context:%o event:%o", context, event);
        let audioItem = this.peekNextAudioItem();
        // 播放铃声
        if (this.playingStreamAudioItem == undefined && audioItem) {
            try {
                await weila_playRing(RingDataID.RING_PLAYER_START_ID);
            } catch (e) {
                PLOG("播放铃声失败:%o", e);
            }
        }

        if (audioItem) {
            try {
                await this.audioCore!.startPlayer(WLPlayerType.STREAM_PLAYER);
                if (this.playingStreamAudioItem == undefined) {
                    this.playingStreamAudioItem = this.getNextPlayingItem();
                }
                this.audioCore!.setPlayerEndCb(this.onStreamItemPlayEndCb.bind(this));
            } catch (e) {
                return Promise.reject(new ServiceError(undefined, e));
            }
        }

        return {
            result: true,
            extra: undefined
        }
    }

    private packetPttMsg(dataList: Uint8Array[], lastPacket: boolean): void {
        let offset = 0;
        let totalLen = 0;
        let currentLen = 0;
        const wlOpusList = [];
        PLOG("打包 %d ptt语音并发送最后一个包 %o", dataList.length, lastPacket);
        while (dataList.length) {
            const opusRawData: Uint8Array = dataList.shift()!;
            const wlOpusData = new Uint8Array(opusRawData.length + 4);
            const flag = opusRawData[0];
            wlOpusData[0] = flag;
            if (flag & 0x80) {
                const wlOpusDataView = new DataView(wlOpusData.buffer);
                wlOpusDataView.setUint16(1, opusRawData.length - 1, false);
                //wlOpusData.set(opusRawData.subarray(1), 3);
                wlOpusData.set(opusRawData.slice(1), 3);
                totalLen += (opusRawData.length + 2);
                currentLen = opusRawData.length + 2;
            }else {
                wlOpusData[1] = opusRawData.length - 1;
                //wlOpusData.set(opusRawData.subarray(1), 2);
                wlOpusData.set(opusRawData.slice(1), 2);
                totalLen += (opusRawData.length + 1);
                currentLen = opusRawData.length + 1;
            }
            //wlOpusList.push(wlOpusData.subarray(0, currentLen));
            wlOpusList.push(wlOpusData.slice(0, currentLen));
        }

        const outputMsgData = new Uint8Array(totalLen);
        wlOpusList.forEach(value => {
            outputMsgData.set(value, offset);
            offset += value.length;
        });

        const pttMsgData = {} as WL_PttPacket;
        pttMsgData.mark = this.talkingInfo!.curMarker;
        pttMsgData.data = outputMsgData;
        pttMsgData.sourceType = 5;
        pttMsgData.frameCount = wlOpusList.length;
        pttMsgData.seq = WL_PttCore.pttSeq;
        pttMsgData.seqInPackage = this.talkingInfo!.seqOfPackage++;

        if (lastPacket) {
            PLOG('最后一帧录音, 分段标志: %o', this.talkingInfo!.curMarker);
            if (this.talkingInfo!.curMarker == WL_PttPackType.PTT_FIRST_PACK) {
                pttMsgData.mark = WL_PttPackType.PTT_WHOLE_PACK;
            }else {
                pttMsgData.mark = WL_PttPackType.PTT_END_PACK;
            }
        }

        this.talkingInfo!.curMarker = lastPacket ? WL_PttPackType.PTT_END_PACK : WL_PttPackType.PTT_INTER_PACK;
        this.listener && this.listener.onRecordPttPacketInd(pttMsgData);
    }

    private packetLastPttMsg(): void {
        PLOG("packetLastPttMsg---------------end talk-----------")
        if (this.talkingInfo!.frameBufferCache.length > 0) {
            while (this.talkingInfo!.frameBufferCache.length >= WL_PttCore.FRAME_COUNT) {
                this.packetPttMsg(this.talkingInfo!.frameBufferCache.slice(0, WL_PttCore.FRAME_COUNT),
                  this.talkingInfo!.frameBufferCache.length == WL_PttCore.FRAME_COUNT);
                this.talkingInfo!.frameBufferCache.splice(0, WL_PttCore.FRAME_COUNT);
            }

            if (this.talkingInfo!.frameBufferCache.length > 0) {
                this.packetPttMsg(this.talkingInfo!.frameBufferCache, true);
            }
        }else {
            this.packetPttMsg([], true);
        }

        this.talkingInfo = undefined;
    }

    private onRecordDataReceiveCb(data: Uint8Array) {
        if (!this.talkingInfo || this.talkingInfo.curMarker == WL_PttPackType.PTT_END_PACK ||
          this.talkingInfo.curMarker == WL_PttPackType.PTT_WHOLE_PACK) {
            this.talkingInfo = undefined;
            return;
        }

        this.talkingInfo.frameBufferCache.push(data);
        if (this.talkingInfo.frameBufferCache.length >= 25) {
            this.packetPttMsg(this.talkingInfo.frameBufferCache.slice(0, WL_PttCore.FRAME_COUNT), false);
            this.talkingInfo.frameBufferCache.splice(0, WL_PttCore.FRAME_COUNT);
        }
    }

    private async startTalk(context: any, event: any): Promise<ServiceResult> {
        PLOG("requestTalk: context:%o event:%o", context, event);
        // 播放铃声
        try {
            await weila_playRing(RingDataID.RING_RECORDER_START_ID)
        }catch (e) {
            PLOG("play talk ring error:%o", e);
        }

        try {
            this.audioCore!.setCodedDataReceiveCb(this.onRecordDataReceiveCb.bind(this));
            await this.audioCore!.startRecorder((error: string) => {
                this.ptt_service.send(FSM_PTT_EVENT_CONSTANT.FSM_TALK_STOP_EVT);
            });
            this.talkingInfo = {} as WL_TalkingInfo;
            this.talkingInfo.callback = event.callback;
            this.talkingInfo.stopByErr = false;
            this.talkingInfo.frameBufferCache = [];
            this.talkingInfo.curMarker = WL_PttPackType.PTT_FIRST_PACK;
            this.talkingInfo.seqOfPackage = 0;
            WL_PttCore.pttSeq++;
        }catch (e) {
            this.audioCore!.setCodedDataReceiveCb(undefined);
            this.talkingInfo = undefined;
            return Promise.reject(new ServiceError(undefined, e));
        }

        return {
            result: true,
            extra: {callback: event.callback}
        };
    }

    private async stopTalk(context: any, event: any): Promise<ServiceResult> {
        PLOG("stopTalk: context:%o event:%o", context, event);
        try {
            await this.audioCore!.stopRecorder();
        }catch (e) {
            PLOG("stopTalk fail:%o", e);
        }finally {
            this.packetLastPttMsg();
            await weila_playRing(RingDataID.RING_RECORDER_STOP_ID);
        }

        return {
            result: true,
            extra: undefined
        };
    }

    private async stopSinglePlayer(context: any, event: any): Promise<ServiceResult> {
        let serviceRet = {} as ServiceResult;
        serviceRet.result = true;
        serviceRet.extra = undefined;
        PLOG("stopSinglePlayer: context:%o event:%o", context, event);
        this.audioCore!.setCodedDataReceiveCb(undefined);
        try {
            await this.audioCore!.stopPlayer(WLPlayerType.SINGLE_PLAYER);
        }catch (e) {
            return Promise.reject(new ServiceError(event.parameter, e));
        }finally {
            await weila_playRing(RingDataID.RING_PLAYER_STOP_ID);

            if (this.listener !== undefined) {
                if (event.msgData) {
                    this.listener.onAudioItemPlayEnd(event.msgData);
                }
            }
        }

        if (this.toNode == FSM_NODE.FSM_SINGLE_PLAY_NODE) {
            serviceRet.extra = event.parameter;
        }else if (this.toNode == FSM_NODE.FSM_TALK_NODE) {
            serviceRet.extra = event.callback;
        }

        return serviceRet;
    }

    private async streamStop(context: any, event: any): Promise<ServiceResult> {
        let ret: boolean = false;
        let serviceErr: ServiceError|undefined;
        PLOG("streamStop: context:%o event:%o", context, event);
        const msgData = this.playingStreamAudioItem!.msgData;
        this.audioCore!.setCodedDataReceiveCb(undefined);
        this.playingStreamAudioItem = undefined;
        try {
            await this.audioCore!.stopPlayer(WLPlayerType.STREAM_PLAYER);
            ret = true;
        }catch (e) {
            serviceErr = new ServiceError(undefined, e);
        }finally {
            if (this.playingAudioList.length > 0) {
                const id = this.playingAudioList.shift();
                if (id) {
                    this.playingAudioMap.delete(id);
                }
            }

            try {
                await weila_playRing(RingDataID.RING_PLAYER_STOP_ID);
            } catch (e) {
                PLOG("播放铃音错误 %o", e);
            }

            if (this.listener !== undefined) {
                this.listener.onAudioItemPlayEnd(msgData);
            }
        }

        if (ret) {
            return {
                result: true,
                extra: undefined
            };
        }

        return Promise.reject(serviceErr);
    }
}


export {WL_PttCore}
